package dnl.util.ui.table;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.table.DefaultTableColumnModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import dnl.util.ui.table.sort.SortBy;
import dnl.util.ui.table.sort.SortDirection;
import dnl.util.ui.table.sort.TableSorter;



/**
 * @author Daniel Or
 * @version 1.0
 */
public class Table extends JPanel {
	
	private JScrollPane sp;
	private TModel tmodel;
	//private ColumnData[] columns;
	private CustomizedTable table = new CustomizedTable();
	private TableSorter tableSorter;
	private Map<String, ColumnKeeper> columnKeepers = new HashMap<String, ColumnKeeper>();
	private boolean doSort;
	private Class<? extends CellRenderer> cellRendererClass;
	private TableCellRenderer cellRenderer;
	
	
	
	public Table(ColumnData[] columns) {
		this(columns, null, null);
	}
	
	public Table(ColumnData[] columns, Class<? extends CellRenderer> rendererClass) {
		this(columns, null, rendererClass);
	}

	public Table(ColumnData[] columns, TableCellRenderer renderer) {
		this(columns, renderer, null);
	}

	private Table(ColumnData[] columns, TableCellRenderer renderer, Class<? extends CellRenderer> rendererClass) {
		super(new BorderLayout());
		//this.columns = columns;
		this.cellRendererClass = rendererClass;
		this.cellRenderer = renderer;
		table.setAutoCreateColumnsFromModel(false);
		setColumnIndexes(columns);
		tmodel = new TModel(columns);
		table.setModel(tmodel);
		// currently internal logic does not support this
		/* @todo add support for reordering columns */
		table.getTableHeader().setReorderingAllowed(false);
		addColumns(columns);
		JTableHeader header = table.getTableHeader();
		header.setUpdateTableInRealTime(false);
		this.sp = new JScrollPane(table);
		this.add(sp, BorderLayout.CENTER);
		tableSorter = new TableSorter(this.table);
		tableSorter.setSortBy(new SortBy(SortDirection.Descending, 0));
	}
			
	
	/**
	 * 
	 *
	 */
	public void scrollCurrentRowToVisible() {
		table.scrollCurrentRowToVisible();
	}
	
	/**
	 * 
	 * @param clax
	 */
	public void setCellRendererClass(Class<? extends CellRenderer> clax){
		this.cellRendererClass = clax;
	}
	
	/**
	 * 
	 * @param from
	 * @param to
	 */
	public void setSelectedRows(int from, int to){
		if(table.getModel().getRowCount() <= from){
		    return;
		}
	    table.setRowSelectionInterval(from, to);
		
	}

	/* (non-Javadoc)
	 * @see java.awt.Component#hasFocus()
	 */
	@Override
	public boolean hasFocus() {
		return table.hasFocus();
	}
	
	public JTable getDelegatedTable(){
		return table;
	}
	
	public void setTableInputMap(int condition, InputMap map){
		table.setInputMap(condition, map);
	}
	
	public InputMap getTableInputMap(int condition){
		return table.getInputMap(condition);
	}
	
	public void setTableActionMap(ActionMap map){
		table.setActionMap(map);
	}
	
	public ActionMap getTableActionMap(){
		return table.getActionMap();
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JComponent#registerKeyboardAction(java.awt.event.ActionListener,
	 *      javax.swing.KeyStroke, int)
	 */
	public void registerKeyboardAction(String command, KeyStroke keyStroke, Action action) {
		table.getInputMap().put(keyStroke, command);
		table.getActionMap().put(command, action);
	}

	/**
	 * @param arg0
	 */
	public void setSurrendersFocusOnKeystroke(boolean arg0) {
		table.setSurrendersFocusOnKeystroke(arg0);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JComponent#unregisterKeyboardAction(javax.swing.KeyStroke)
	 */
	@Override
	public void unregisterKeyboardAction(KeyStroke arg0) {
		table.unregisterKeyboardAction(arg0);
	}
		
	/**
	 * 
	 * @param column
	 * @param comparator
	 */
	public void setPredefinedRowComparator(int column, Comparator<?> comparator){
		tableSorter.setPredefinedComparator(column, comparator);
	}
	
	
	/**
	 *  
	 */
	@Override
	public void setBackground(Color color) {
		super.setBackground(color);
		if (table != null)
			table.setBackground(color);
		if (sp != null)
			this.sp.getViewport().setBackground(color);
	}

	/**
	 * Returns a <code>RowData</code> for the currently selected row.
	 * 
	 * @return RowData
	 */
	public RowData getSelectedRowData() {
		return table.getSelectedRowData();
	}

	public RowData getRowData(int index) {
		return table.getRowData(index);
	}
	
	public int getRowCount(){
		return table.getTModel().getRowCount();
	}
	
	public RowData[] getData(){
	    return table.getTModel().getData();
	}

	/**
	 * @param rowData
	 */
	public void setData(RowData[] rowData) {
		List<RowData> al = Arrays.asList(rowData);
		if (doSort) {
			//Collections.sort(al, rowComparator);
		}
		RowData[] rds = al.toArray(new RowData[al.size()]);
		tmodel.setData(rds);
		if(SortBy.NONE.equals(tableSorter.getSortBy()) ){
			//tableSorter.setSortBy(new SortBy(SortDirection.Descending, 0));
		}
		tableSorter.applySort();
	}

	/**
	 * @param cellEditor
	 */
	public void setCellEditor(TableCellEditor cellEditor) {
		table.setCellEditor(cellEditor);
	}

	/**
	 * @param rowEditor
	 */
	public void setExternalEditor(ExternalRowEditor rowEditor) {
		table.setExternalEditor(rowEditor);
	}

	/**
	 *  
	 */
	public void editSelectedRow() {
		table.editSelectedRow();
	}

	/**
	 * @param component
	 */
	public void addToPopup(JComponent component) {
		table.addToPopup(component);
	}

	public void setPopupMenu(JPopupMenu jpop) {
		table.setPopupMenu(jpop);
	}

	public void setShowSelectionBorder(boolean show) {
		table.setShowSelectionBorder(show);
	}

	public void setShowGrid(boolean show) {
		table.setShowGrid(show);
	}

	public boolean isSelectionBorderShown() {
		return table.isSelectionBorderShown();
	}

	public void setSortData(boolean doSort) {
		this.doSort = doSort;
	}

	public void addListSelectionListener(ListSelectionListener listener) {
		table.getSelectionModel().addListSelectionListener(listener);
	}

	public void enableMultipleSelection(boolean enable) {
		if (enable)
			table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
		else
			table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
	}

	public void setGridColor(Color newGridColor) {
		table.setGridColor(newGridColor);
	}

	public JMenu getColumnSelectionMenu(String menuName) {
		JMenu menu = new JMenu(menuName);
		TableColumnModel colModel = table.getColumnModel();
		for (int k = 0; k < tmodel.getColumnCount(); k++) {
			JCheckBoxMenuItem item = new JCheckBoxMenuItem(tmodel.getColumnName(k));
			item.setSelected(true);
			TableColumn column = colModel.getColumn(k);
			item.addActionListener(new ColumnKeeper(column, tmodel.getColumnData(k)));
			menu.add(item);
		}
		return menu;
	}

	public void setColumnShown(String columnName, boolean show) {
		ColumnKeeper ck = columnKeepers.get(columnName);
		if (ck == null)
			return;
		ck.setShown(show);
	}
	
	public void packAllColumns(){
		int sum = 0;
		
		for (int c=0; c<table.getColumnCount()-1; c++) {
			sum += packColumn(c, 2);
		}		
	}

	private int packColumn(int vColIndex, int margin) {
		DefaultTableColumnModel colModel = (DefaultTableColumnModel)table.getColumnModel();
		TableColumn col = colModel.getColumn(vColIndex);
		int width = 0;
		
		// Get width of column header
		TableCellRenderer renderer = col.getHeaderRenderer();
		if (renderer == null) {
			renderer = table.getTableHeader().getDefaultRenderer();
		}
		Component comp = renderer.getTableCellRendererComponent(
				table, col.getHeaderValue(), false, false, 0, 0);
		width = comp.getPreferredSize().width;
		
		// Get maximum width of column data
		for (int r=0; r<table.getRowCount(); r++) {
			renderer = table.getCellRenderer(r, vColIndex);
			comp = renderer.getTableCellRendererComponent(
					table, table.getValueAt(r, vColIndex), false, false, r, vColIndex);
			width = Math.max(width, comp.getPreferredSize().width);
		}
		
		// Add margin
		width += 2*margin;
		
		// Set the width
		col.setMaxWidth(width);
		return width;
	}

	private void setColumnIndexes(ColumnData[] columns) {
		for (int i = 0; i < columns.length; i++) {
			columns[i].setIndex(i);
		}
	}

	/**
	 * @param columns
	 */
	private void addColumns(ColumnData[] columns) {
		//
		for (int i = 0; i < columns.length; i++) {
			TableCellRenderer tcr = cellRenderer;
			if(tcr == null)
				tcr = newCellRenderer(columns[i]);
			TableColumn column = new TableColumn(i, columns[i].width, tcr, null);
			table.addColumn(column);
		}
	}
	
	private TableCellRenderer newCellRenderer(ColumnData cdata) {
		CellRenderer cellRenderer = null;
		
		if(cellRendererClass == null){
			cellRenderer = new CellRenderer();
			cellRenderer.setHorizontalAlignment(cdata.alignment);
		}
		else {
			try {
				return cellRendererClass.newInstance();
			}
			catch (Exception e) {
				cellRenderer = new CellRenderer();
				cellRenderer.setHorizontalAlignment(cdata.alignment);
			}
		}
		return cellRenderer;
	}


	/**
	 *  
	 */
	private class ColumnKeeper implements ActionListener {

		TableColumn column;
		ColumnData cData;
		boolean isShown = true;

		public ColumnKeeper(TableColumn column, ColumnData cData) {
			this.column = column;
			this.cData = cData;
			columnKeepers.put(cData.getTitle(), this);
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			JCheckBoxMenuItem item = (JCheckBoxMenuItem) e.getSource();
			//TableColumnModel model = table.getColumnModel();
			setShown(item.isSelected());
		}

		public void setShown(boolean show) {
			TableColumnModel model = table.getColumnModel();
			if (show && !isShown) {
				model.addColumn(column);
				model.moveColumn(model.getColumnCount() - 1, cData.getIndex());
				isShown = true;
			}
			else if (!show) {
				model.removeColumn(column);
				isShown = false;
			}
			table.tableChanged(new TableModelEvent(tmodel));
			table.repaint();
		}
	}
	
	
	// -----------------  Delegated Methods ----------------------------------------------
	// ===================================================================================

	public ListSelectionModel getSelectionModel(){
		return table.getSelectionModel();
	}

	public void unfocusHidesSelectedLine(boolean unfocusHides){
		table.unfocusHidesSelectedLine(unfocusHides);
	}
	
	public void setSelectedObject(Object obj, int column){
		table.setSelectedObject(obj, column);
	}
	
	public void setSelectionMode(int selectionMode){
		table.setSelectionMode(selectionMode);
	}

	public int[] getSelectedRows(){
		return table.getSelectedRows();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JComponent#getConditionForKeyStroke(javax.swing.KeyStroke)
	 */
	@Override
	public int getConditionForKeyStroke(KeyStroke arg0) {
		return table.getConditionForKeyStroke(arg0);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JComponent#getRegisteredKeyStrokes()
	 */
	@Override
	public KeyStroke[] getRegisteredKeyStrokes() {
		return table.getRegisteredKeyStrokes();
	}

}